/**
 * @file breathing.c
 * @author TimeChicken (https://gitee.com/Time--Chicken)
 * @brief 报文库解析函数实现
 * @version 0.1
 * @date 2021-08-23
 * 
 * @copyright Copyright (c) 2021
 * 
 */
#include "breathing.h"

/**
 * @brief 监听线程服务
 * 
 * @param argv 无
 * @return void* 无
 */
void *wait_for_otherClient_to_respond(void *argv)
{

    sockaddr_in local;

    local.sin_family = AF_INET;

    local.sin_addr.s_addr = INADDR_ANY;
    local.sin_port = htons(DEST_PORT); //监听端口

    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
        ERR_EXIT("socket");

    int optval = 1; // 设置套接字属性
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    int ret = bind(sockfd, (sockaddr *)&local, sizeof(local)); // 绑定端口
    if (ret < 0)
        ERR_EXIT("bind");

    char buf[REC_MAX] = {0};
    sockaddr_in client;
    socklen_t len = sizeof(client);
    ssize_t rec_len;
    data_packet *d_p; //定义一个数据包指针

    while (1)
    {
        rec_len = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (sockaddr *)&client, &len);
        // printf("收到一个包。%ld\n", rec_len);
        // printf("rec_to_type:%d\n", ((data_packet *)buf)->packet_type);
        if (rec_len > 0) // 如果收到数据，就往包里放数据
        {
            d_p = malloc(rec_len);
            bzero(d_p, rec_len);
            memcpy(d_p, buf, rec_len);
            // printf("s=%s\n", buf + 20);
            d_p->ip = client.sin_addr.s_addr; // 以网络字节序存储IP
            d_p->port = client.sin_port;      // 以网络字节序存储端口号

            if (msgQueue_in_dataPacket(d_p) < 0) // 数据包入消息队列
                perror("insert queue fail");
        }
    }
}

/**
 * @brief 消息处理服务
 * 
 * @param argv no use
 * @return void* nerver return
 */
void *msg_processing(void *argv)
{
    data_packet *out_d_p;

    while (1)
    {
        out_d_p = msgQueue_out_dataPacket();
        if (out_d_p == NULL)
            continue;
        // 如果有消息的话就处理
        // printf("有消息来了。%d\n", out_d_p->packet_type);
        switch (out_d_p->packet_type)
        {
        case PACK_ONLINE:
            friends_online(out_d_p); // 处理上线
            break;
        case PACK_ONLINE_REC:
            friends_online_rec(out_d_p); // 处理收到上线响应
            break;
        case PACK_OFFLINE:
            friends_offline(out_d_p); // 处理下线报文
        case PACK_MSG:
            rec_msg(out_d_p); // TODO 处理发送消息
            break;
        case PACK_MSG_FILE:
            // TODO 处理发送文件的请求
            break;
        default:
            // 如果是未知报文，丢弃
            free(out_d_p);
            break;
        }
    }
}

/**
 * @brief 响应上线报文
 * 
 * @param d_p 数据包
 */
void friends_online(data_packet *d_p)
{
    struct in_addr inp;
    inp.s_addr = d_p->ip;
    printf("\n好友:%s上线了。\n", inet_ntoa(inp)); //屏幕输出用户上线信息
    d_p->packet_type = PACK_ONLINE_REC;            //直接设置为响应数据
    d_p->port = htons(DEST_PORT);                  //指定返回的端口
    send_data(d_p, NULL);                          //因为已经有了ip，所以不指定
}

/**
 * @brief 处理上线的上线响应报文，添加至上线好友
 * 
 * @param d_p 数据包地址
 */
void friends_online_rec(data_packet *d_p)
{
    friends_add(d_p->ip);
    free(d_p);
}

/**
 * @brief 下线报文
 * 
 * @param d_p 数据包地址
 */
void friends_offline(data_packet *d_p)
{
    struct in_addr inp;
    inp.s_addr = d_p->ip;
    friends_del(d_p->ip);
    printf("\n好友%s下线了。\n", inet_ntoa(inp));
    // free(d_p);
}

/**
 * @brief 接收好友发送的消息，并直接打印在屏幕上
 * 
 * @param d_p 数据包地址
 */
void rec_msg(data_packet *d_p)
{
    struct in_addr inp;
    inp.s_addr = d_p->ip;
    char *s = (char *)d_p + 20;
    // printf("\n%s:%hd:%s\n", inet_ntoa(inp), ntohs(d_p->port), s);
    printf("\n%s:%hd:%s\n", inet_ntoa(inp), d_p->port, s);
    free(d_p);
}

/**
 * @brief 发送数据包，发送后参数中的地址内存都会被释放
 * 
 * @param d_p 发送的包头
 * @param data 发送的数据
 * @param data_len 发送数据的长度
 */
void send_data(data_packet *send_d_p, char *send_d)
{
    // char buf[MAX_LENGTH] = {0};
    char *buf = malloc(MAX_LENGTH);
    sockaddr_in broadcast_addr;

    broadcast_addr.sin_family = AF_INET;
    broadcast_addr.sin_addr.s_addr = send_d_p->ip; //广播地址
    broadcast_addr.sin_port = send_d_p->port;      //发送指定端口

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建套接字
    if (sockfd < 0)
        ERR_EXIT("socket");

    int optval = 1;                                                        // 这个值一定要设置，否则可能导致sendto()失败
    setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval)); //允许广播
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); //套接字重用
    // printf("start broadcast!!!\n");

    // 封装数据包
    memcpy(buf, send_d_p, 20); //将数据包的数据放到开头20字节中
    if (send_d != NULL)
    {
        char *data = (char *)buf + 20;
        memcpy(data, send_d, send_d_p->length);
    }

    // 发送后关闭
    int res = sendto(sockfd, buf, send_d_p->length + 20, 0, (sockaddr *)&broadcast_addr, sizeof(broadcast_addr));
    // printf("res:%d\n", res);//debug
    // printf("send_s=%s\n", buf + 20);

    shutdown(sockfd, SHUT_RDWR); //优雅的关闭socket链接
    close(sockfd);               //关闭socke描述符

    // free(send_d_p); //释放所有控件
    free(buf);
}